/**
 * AreaData.java
 *
 * @author Hagen Boehm
 */


package SSF.OS.OSPFv2;


import com.renesys.raceway.DML.*;
import SSF.OS.ProtocolException;
import SSF.Net.*;
import SSF.Net.Util.IP_s;
import java.util.*;

// ======================= class SSF.OS.OSPFv2.AreaData ===================== //
/**
 * This Data Stucture contains all information used to run the basic OSPF routing
 * algorithm.
 */
public class AreaData implements Configurable {

  // ...................... member data ...................... //

  /** The identifier for this area. */
  public int areaID;

  /** A reference to the OSPF session this area belongs to. */
  public OSPF myOSPF;

  /** This Vector holds the areas address ranges in order to aggregate routing
   *  information at area boundaries. */
  public Vector addressRanges;

  /** The following HashMap represents a list of all router's interfaces associated 
   *  with this area. Key is the ip address of the correspondig interface. 
   */
  public HashMap interfaces;

  /** A HashMap representing a list of all router's pre-configured interfaces associated 
   *  with this area. Key is the interface's id found in the configuration.
   */
  public HashMap preConfInterfaces;

  /** The LS Database for this area. It includes router-LSAs, network-LSAs and
   *  summary-LSAs. AS-external-LSAs are hold in the OSPF class itself. */
  public LSDatabase database;

  /** The shortest-path tree for the area represented as HashMap. */
  public HashMap shortestPathTree;

  /** A instance of the dijkstra algorithm to calculate shortest paths for this area. */
  public Dijkstra dijkstra;

  /** This parameter indicates whether the area can carry data traffic that neither
   *  originates nor terminates in the area itself (Is a transit area or not). 
   *  For more description see RFC 2328 chapter 6 
   */
  public boolean transitCapability;

  /** Indicates the cost of the default summary-LSA, the router advertises if eBit
   *  is false and the router itself is an area border border. */
  public int stubDefaultCost;

  /** The optional capabilities of this area of the router. The eBit is part of
   *  this array and comes at position 6. See RFC2328 A.2 The Options field.
   */
  public boolean[] options;

  /** boolean value indicating that the Area configuration has changed. */
  private boolean configChanged = false;

  // ---------------------------- AreaData.config ------------------------------- //
  /** Configure the ospf area parameters. Supported DML attributes are:
   *  <PRE>
   *  area [
   *         # The area's id as an integer, not formatted as an IP address
   *         # optional only if the router is solely attached to the backbone area.
   *         # In this case default value is 0 else 
   *         # mandatory!
   *         id      %I
   *
   *         # This can be used to configure multiple areas at once
   *         idrange [from %I1! to %I1!] # idrange not impl. yet!!!
   *
   *	     # Specifies if this area is a stub area (true|false)
   *         # optional! default value is: false
   *         stub    %S
   *           
   *         # Specifies the StubDefaultCost for a stub area
   *         # optional! default value is: 0
   *         stub_default_cost %I
   *
   *         # A router's interfaces all belong to an area. All interfaces belonging
   *         # to this area are specified here.
   *         if %T:.schemas.ospf_if
   *       ]
   *  </PRE>
   *
   * @param cfg contains the values for configurable ospf area parameters
   * @exception configException  thrown if any of the calls to <code>findSingle</code> and
   *                             <code>find</code> throw such an exception or if a 
   *                             misconfiguration is determined. 
   */
  public void config(Configuration cfg) throws configException {
    HashMap dynConfKeys = new HashMap();
    boolean isStub;
    // An Enumeration to save the individual configurations of the Interfaces
    Enumeration intfConfData;
    // current Interface Configuration
    Configuration nextConfig;
    // IP address and ID of the current Interface
    Integer nextIntfIP, nextIntfID;
    // boolean indicating whether this is the first configuration
    boolean isFirst = false;

    for(Enumeration k = ((dmlConfig)cfg).keys();k.hasMoreElements();) {
      String ak = (String)k.nextElement();
      dynConfKeys.put(ak, new Integer(1));
    }

    if (dynConfKeys.containsKey("id")) {
      areaID = (new Integer((String)cfg.findSingle("id"))).intValue();
      configChanged = true;
    }
    if (dynConfKeys.containsKey("stub")) {
      isStub = (new Boolean((String)cfg.findSingle("stub"))).booleanValue();
      if (isStub && (areaID == 0)) throw new configException("Backbone area configured as stub!");
      options[6] = !isStub;
      configChanged = true;
    }
    if (options[6] == false) {
      if (dynConfKeys.containsKey("stub_default_cost")) {
	stubDefaultCost = (new Integer((String)cfg.findSingle("stub_default_cost"))).intValue();
        configChanged = true;
      }
    }
    if (dynConfKeys.containsKey("if")) {
      intfConfData = (Enumeration)cfg.find("if");
      // a reference to the Hashtable containing the mappings of IDs and NICs of the router.
      Hashtable nics = myOSPF.rtr.interfaceNumbers;
      // if no NICs are defined this must be the first configuration!
      isFirst = nics.isEmpty();
      while (intfConfData.hasMoreElements()) {
        // find a new Interface configuration
        nextConfig = (Configuration)intfConfData.nextElement();
        if (nextConfig.findSingle("id") != null) 
	  // find ID of Interface next to configure
          nextIntfID = new Integer((String)nextConfig.findSingle("id"));
        else throw new configException("No Interface id set!");
        if (nics.containsKey(nextIntfID)) {
	  // this is a reconfiguration of the current interface, so find IP of Interface next to 
	  // configure.
          nextIntfIP = new Integer(((NIC)nics.get(nextIntfID)).ipAddr);
          if (interfaces.containsKey(nextIntfIP)) {
	    // the Interface to configure belongs already to this area
            Interface reconfIntf = (Interface)interfaces.get(nextIntfIP);
            reconfIntf.config(nextConfig);
            reconfIntf.printConfiguration();
          } else {
            // check all other areas whether the Interface belongs to one of them
            for (Iterator nbAreas = myOSPF.areaStructs.values().iterator(); nbAreas.hasNext();) {
              AreaData nextArea = (AreaData)nbAreas.next();
              if (nextArea.equals(this)) continue; // skip this Area
              if (nextArea.hasInterface(nextIntfIP)) {
	        // found Area to which the Interface former belongs
                // remove Interface from former area, put it in this area ...
                interfaces.put(nextIntfIP, nextArea.remInterface(nextIntfIP));
                // ... and reconfigure it.
                Interface changedIntf = (Interface)interfaces.get(nextIntfIP);
                changedIntf.setArea(this);
                changedIntf.config(nextConfig);
                changedIntf.printConfiguration(); 
                if (nextArea.interfaces.isEmpty())
		  // former area is empty now. Remove former area from OSPF session.
		  myOSPF.areaStructs.remove(new Integer(nextArea.areaID));
                break;
              }
            }
          }
        } else {
	  // NIC is yet not configured 
	  if (preConfInterfaces.containsKey(nextIntfID))
	      System.err.println("Warning! Interfaces ID "+nextIntfID.intValue()+" already configured. Interface configuration overridden!");
	  // preconfigure this interface and put it into HashMap preConfInterfaces
	  preConfInterfaces.put(nextIntfID, new Interface(myOSPF, this, nextConfig));
	}
      }
    }

    // Print Area configuration if necessary.
    if (!isFirst) printConfiguration();
  }

  // -------------------------- constructor AreaData -------------------------- //
  /**
   * Constructs an Area Data Structure with the specified area id. 
   */
  public AreaData(OSPF ospf, Configuration cfg) {
    areaID = 0;
    myOSPF = ospf;
    addressRanges = new Vector();
    interfaces = new HashMap();
    preConfInterfaces = new HashMap();
    database = new LSDatabase(ospf, this);
    transitCapability = false;
    options = new boolean[8];
    // set the e-bit to true
    options[6] = true;
    stubDefaultCost = 0;
    configChanged = true;
    try {
      config(cfg);
    } catch (configException cex) {
      myOSPF.printConfigError("Area", (new Integer(areaID)).toString(), cex);
    }
  }

  // ----------------------------- AreaData.configRest ---------------------------------- //
  /**
   * Additional configuration to the area. Configure IP addresses, IP maskes and the ospf 
   * neighbors of all interfaces belonging to this area and check their configured network type.
   * 
   * @exception configException if a misconfiguration is determined and if the called 
   *                            configuration routines throw such exceptions.
   */
  public void configRest() throws configException {
    NIC nextRealInterface;
    Interface nextOspfInterface;
    Enumeration intfPeers;
    // String peer_asnh;
    // String peer_nh = "null";

    Object[] ospfIntfIDs = preConfInterfaces.keySet().toArray();
    for (int i=0; i < ospfIntfIDs.length; i++) {
      nextOspfInterface = (Interface)preConfInterfaces.remove(ospfIntfIDs[i]);
      if (!myOSPF.rtr.interfaceNumbers.containsKey(nextOspfInterface.id))
	myOSPF.printConfigError("Area", areaID, "Interface ID " + 
                                nextOspfInterface.id.toString() + " does not match any NIC!");
      nextRealInterface = (NIC)myOSPF.rtr.interfaceNumbers.get(nextOspfInterface.id);
      if (nextRealInterface.link_hw != null) {
	intfPeers = nextRealInterface.link_hw.peers(nextRealInterface);
      } else {
        // in this case the interface is considered as a "virtual interface" and has no peers.
	intfPeers = null;
      }
      try {
        nextOspfInterface.configRest(intfPeers);
      } catch(configException cex) {
        myOSPF.printConfigError("Interface", nextOspfInterface.id.toString(), cex);
      }
      interfaces.put(new Integer(nextOspfInterface.ipaddr), nextOspfInterface);
    }

    // Print Area configuration if necessary.
    printConfiguration();
  }

  // -------------------------------- AreaData.init ------------------------------------- //
  /** 
   * Start the ospf routing process in this area. Therefore call "init()" of all attached
   * interfaces and create a router LSA. Also used when restarting ospf.
   */
  public void init() {
    // activate all Interfaces to run OSPF.
    for (Iterator ospfIntfs = interfaces.values().iterator(); ospfIntfs.hasNext(); ) {
      ((Interface)ospfIntfs.next()).init();
    }

    // generate the first Router-LSA for this Area.
    createRouterLSA();
  }

  // ------------------------------ AreaData.reset ------------------------------------- //
  /**
   *  Reset the Area Data Structure. That means set a few references to the initial values
   *  and reset all Interfaces. This is part of the stop process of the OSPF Session. 
   */
  public void reset() {
    for (Iterator ifs = interfaces.values().iterator(); ifs.hasNext(); ) {
      ((Interface)ifs.next()).reset();
    }
    shortestPathTree = null;
    dijkstra = null;
    database.clear();
  }

  // --------------------------- AreaData.setAddressRanges ------------------------------ //
  /** Set the areas address ranges to a new collection of address ranges. */
  public void setAddressRanges(Vector addrRanges) {
    addressRanges = addrRanges;
  }

  // -------------------------- AreaData.addAddrRange ---------------------------------- //
  /** adds a new address range to the given. The class Object should be replaced by an 
   *  appropriate class for address Ranges in the future. */
  public void addAddrRange(Object addrRange) {
    addressRanges.add(addrRange);
  }

  // -------------------------- AreaData.addInterface --------------------------------- //
  /** adds a new Interface to this area. */
  public void addInterface(Interface intf) {
    interfaces.put(new Integer(intf.ipaddr), intf);
  }

  // -------------------------- AreaData.remInterface --------------------------------- //
  /** removes an Interface from the Interface list cause of an area change while re-
   *  configuring this Interface. */
  public Interface remInterface(Integer intfIP) {
    return ((Interface)interfaces.remove(intfIP));
  }

  // -------------------------- AreaData.hasInterface --------------------------------- //
  /** called by the OSPF session by order of another Area Data Structure to determine
   *  whether a particular Interface belongs to this Area. This is needed to handle a 
   *  change in membership in an area of an Interface correctly.
   *  The method returns true if the Interface, specified by its key, belongs to this
   *  Area, otherwise false.
   */
  public boolean hasInterface(Integer ip) {
    if (interfaces.containsKey(ip)) return true;
    return false;
  }

  // ------------------------- AreaData.createRouterLSA ------------------------------ //
  /** create a new router LSA for this area, install it in the database and if possible 
   *  send it out the appropriate interfaces. 
   */
  public void createRouterLSA() {
    LSA lsaHeader;
    Router_LSA lsaBody;
    Interface actInterface;
    Neighbor actNeighbor;
    boolean wasInstalled = false;
    String lsaKey = LSA.ROUTER_LSA + " " + IP_s.IPtoString(myOSPF.rtrid) + " " + 
                    IP_s.IPtoString(myOSPF.rtrid);
    boolean prematureAge = false;
    int seqNum;

    // first create a new body.
    lsaBody = new Router_LSA(false, myOSPF.as_boundary_rtr, myOSPF.area_border_rtr);
    for (Iterator intfs = (interfaces.values()).iterator(); intfs.hasNext();) {
      actInterface = (Interface)intfs.next();
      if (actInterface.state == Interface.DOWN) continue;
      if (actInterface.state == Interface.LOOPBACK) {
        lsaBody.addLink(new LinkInfo(actInterface.ipaddr, -1, myOSPF.TO_STUB_NET, 0));
        continue;
      }
      if (actInterface.networkType == Interface.PTP) {
        // Describing point-to-point interfaces.
        Integer peerKey = (Integer)actInterface.neighbors.keySet().iterator().next();
        actNeighbor = (Neighbor)actInterface.neighbors.get(peerKey);
        if (actNeighbor.state == Neighbor.FULL) {
          // The neighboring router is fully adjacent.
          lsaBody.addLink(new LinkInfo(actNeighbor.neighborID, actInterface.ipaddr, 
                                       myOSPF.POINT_TO_POINT, actInterface.cost));
        }
        if (actInterface.state == Interface.POINT2POINT) {
	  // RFC 2328 12.4.1.1. Option 1:
	    // Host routes aren't configurable, so proceed on
          // RFC 2328 12.4.1.1. Option 2:
          lsaBody.addLink(new LinkInfo(actInterface.ipaddr & actInterface.ipmask, actInterface.ipmask,
                          myOSPF.TO_STUB_NET, actInterface.cost));
        }
      }
    }

    // second create a new header.
    seqNum = database.getNextSeqNum(lsaKey, LSA.ROUTER_LSA);
    if(seqNum == OSPF.MAX_SEQUENCE_NUM + 1) {
      seqNum--;
      prematureAge = true;
    }
    lsaHeader = new LSA(options, LSA.ROUTER_LSA, myOSPF.rtrid, myOSPF.rtrid, myOSPF.nh, seqNum);
    if (prematureAge) lsaHeader.lsAge = OSPF.MAX_AGE;

    // third compose the LSA, install it and possibly flood it out.
    lsaHeader.carryPayload(lsaBody);
    installSelfOrigLSA(lsaHeader);
    databaseDebugInfo();
  }

  // --------------------------- AreaData.createSummary3LSA ---------------------------------- //
  /** create a new summary type 3 LSA for this area and return it.
   *  @param ipAddress int value, representing the ip address of the destination network.
   *  @param ipMask int value, representing the ip mask of the destination network.
   *  @param cost int value, indicating the OSPF cost of the shortest path to the destination.
   *  @return LSA Object containing a summary LSA of type 3.
   */
  public LSA createSummary3LSA(int ipAddress, int ipMask, int cost) {
    LSA lsaHeader;
    Summary_LSA lsaBody;
    String lsaKey;
    boolean prematureAge = false;
    int seqNum;

    lsaKey = LSA.SUMMARY_NETWORK_LSA+" "+IP_s.IPtoString(ipAddress)+" "+IP_s.IPtoString(myOSPF.rtrid);

    lsaBody = new Summary_LSA(ipMask, cost);

    seqNum = database.getNextSeqNum(lsaKey, LSA.SUMMARY_NETWORK_LSA);
    if(seqNum == OSPF.MAX_SEQUENCE_NUM + 1) {
      seqNum--;
      prematureAge = true;
    }
    lsaHeader = new LSA(options, LSA.SUMMARY_NETWORK_LSA, ipAddress, myOSPF.rtrid, myOSPF.nh, seqNum);
    if (prematureAge) lsaHeader.lsAge = OSPF.MAX_AGE;

    lsaHeader.carryPayload(lsaBody);
    return lsaHeader;
  }

  // --------------------------- AreaData.createSummary4LSA ---------------------------------- //
  /** create a new summary type 4 LSA for this area and return it. This method works similar to
   *  "createSummary3LSA()".
   *  @param ipAddress int value, representing the ip address of the destination router.
   *  @param cost int value, indicating the OSPF cost of the shortest path to the destination router.
   *  @return LSA Object containing a summary LSA of type 4.
   */
  public LSA createSummary4LSA(int ipAddress, int cost) {
    LSA lsaHeader;
    Summary_LSA lsaBody;
    String lsaKey;
    boolean prematureAge = false;
    int seqNum;

    lsaKey = LSA.SUMMARY_ASBR_LSA+" "+IP_s.IPtoString(ipAddress)+" "+IP_s.IPtoString(myOSPF.rtrid);

    lsaBody = new Summary_LSA(cost);

    seqNum = database.getNextSeqNum(lsaKey, LSA.SUMMARY_ASBR_LSA);
    if(seqNum == OSPF.MAX_SEQUENCE_NUM + 1) {
      seqNum--;
      prematureAge = true;
    }
    lsaHeader = new LSA(options, LSA.SUMMARY_ASBR_LSA, ipAddress, myOSPF.rtrid, myOSPF.nh, seqNum);
    if (prematureAge) lsaHeader.lsAge = OSPF.MAX_AGE;

    lsaHeader.carryPayload(lsaBody);
    return lsaHeader;
  }

  // --------------------------- AreaData.removeSummaryLSA -------------------------------- //
  /** Remove a selforiginated Summary-LSA from the LS Database. This is done by premature aging
   *  the LSA if it exists, otherwise nothing is done.
   *  @param type byte value indicating a type 3 or 4 LSA.
   *  @param destKey Object (MUST be String in case of type 3 LSA, Integer in case of type 4)
   *                 indicating the destination id.
   */
  public void removeSummaryLSA(byte type, Object destKey) {
    if (type == LSA.SUMMARY_NETWORK_LSA) {
      String[] destIDs = ((String)destKey).split("/");
      database.prematureAgeLSA(type, Integer.parseInt(destIDs[0]));
      return;
    }
    if (type == LSA.SUMMARY_ASBR_LSA) {
      int destID = ((Integer)destKey).intValue();
      database.prematureAgeLSA(type, destID);
      return;
    }
  }

  // --------------------------- AreaData.installSelfOrigLSA --------------------------------- //
  /** Try to install a self-originated LSA and if the installation process was successful, flood
   *  it out some appropriate interfaces.
   *  @param soLSA self-originated LSA to install.
   */
  public void installSelfOrigLSA(LSA soLSA) {
    boolean wasInstalled = database.installOwnLSA(soLSA);
    if (wasInstalled) floodLSA(soLSA, myOSPF.rtrid);
  }

  // ---------------------------- AreaData.advanceSeqNumOf ----------------------------------- //
  /** Method called by "Neighbor.processReceivedLSA() in case of receiving a selforiginated LSA
   *  newer than the one stored in the Database. This method implements most of chapter 13.4
   *  of RFC 2328.
   *
   *  @param advLSA The received selforiginated LSA.
   */
  public void advanceSeqNumOf(LSA advLSA) {
    Summary_LSA body;
    if (database.hasInstanceOf(advLSA)) {
      // there is already a copy in the database -> advance to next sequence number. 
      database.setNextSeqNum(advLSA.getKey(), advLSA.lsType, advLSA.lsSeqnum + 1);
      switch (advLSA.lsType) {
      case LSA.ROUTER_LSA:
	createRouterLSA();
      break;
      case LSA.SUMMARY_NETWORK_LSA:
	body = (Summary_LSA)advLSA.payload();
        if (body != null) {
	  LSA sumLSA;
          int cost = myOSPF.routingTable.getNetCost(advLSA.lsID + "/" + body.netMask, areaID);
          if (cost != -1) {
	    // There is a route to this network in the OSPFv2 routing table
	    sumLSA = createSummary3LSA(advLSA.lsID, body.netMask, cost);
            installSelfOrigLSA(sumLSA);
          } else {
            // NO route to this network in the OSPFv2 routing table
            sumLSA = createSummary3LSA(advLSA.lsID, body.netMask, body.metric);
            sumLSA.lsAge = OSPF.MAX_AGE;
            installSelfOrigLSA(sumLSA);
          }
          databaseDebugInfo();
        }
      break;
      case LSA.SUMMARY_ASBR_LSA:
        body = (Summary_LSA)advLSA.payload();
        if (body != null && options[6]) {
	  LSA sumLSA;
          int cost = myOSPF.routingTable.getRoutCost(new Integer(advLSA.lsID), areaID);
          if (cost != -1) {
	    // There is a route to the ASBR in the OSPFv2 routing table
	    sumLSA = createSummary4LSA(advLSA.lsID, cost);
            installSelfOrigLSA(sumLSA);
          } else {
            // NO route to the ASBR in the OSPFv2 routing table
            sumLSA = createSummary4LSA(advLSA.lsID, body.metric);
            sumLSA.lsAge = OSPF.MAX_AGE;
            installSelfOrigLSA(sumLSA);
          }
          databaseDebugInfo();
        }
      break;
      default:
	String warning = "Received LSA ("+advLSA.getKey()+") of unknown type: "+ advLSA.lsType+
                          ". LSA is dropped!";
	myOSPF.printGeneralWarning(warning);
      break;
      }
    } else {
      // there was no copy in the database.
      switch (advLSA.lsType) {
      case LSA.SUMMARY_NETWORK_LSA:
	// premature age LSA...
        advLSA.lsAge = myOSPF.MAX_AGE;
        // ...install and reflood it.
	installSelfOrigLSA(advLSA);
        databaseDebugInfo();
      break;
      case LSA.SUMMARY_ASBR_LSA:
	// in case the area is not a stub...
	if (options[6]) {
          // ...premature age LSA...
          advLSA.lsAge = myOSPF.MAX_AGE;
          // ...install and reflood it.
	  installSelfOrigLSA(advLSA);
          databaseDebugInfo();
        }
      break;
      default:
	if (advLSA.lsType != 1) {
	  String warning = "Received LSA ("+advLSA.getKey()+") of unknown type: "+ advLSA.lsType+
                           ". LSA is dropped!";
	  myOSPF.printGeneralWarning(warning);
        }
      break;
      }
    }
  }

  // ------------------------------ AreaData.floodLSA ----------------------------------- //
  /** Flood an LSA out some set of the router's interfaces. This is the implementation
   *  of chapter 13.3 of RFC 2328.
   *  @param lsa LSA that must be flooded out some interfaces.
   *  @param neighborID int value, identifying the neighbor from whom the LSA was received.
   */
  public void floodLSA(LSA lsa, int neighborID) {
    if (lsa.lsType == LSA.AS_EXTERNAL_LSA) {
      myOSPF.floodExternalLSA(lsa);
      return;
    }
    Interface nextInterface;
    for (Iterator itfs = interfaces.values().iterator(); itfs.hasNext(); ) {
      nextInterface = (Interface)itfs.next();
      nextInterface.pacingTimer.addLSA(lsa, neighborID);
    }
  }

  // ------------------------- AreaData.getLSAHeaders --------------------------------- //
  /** Return a Vector containing all LSA-Headers currently stored in the LS Database.
   *  This method is used by a Neighbor Data Structure to gain access to the LSAs of
   *  its Area. This is necessary to fill the Database Summary List of the Neighbor
   *  Structure calling this method when going in state EXCHANGE.
   */
  public Vector getLSAHeaders() {
    return database.getAllLSAHeaders();
    // Does not return AS external LSAs
  }

  // -------------------------- AreaData.shouldRequest ------------------------------ //
  /** Take an LSA Header and determine whether the associated body should be requested
   *  or not. Return true if the LSA must be requested else false.
   */
  public boolean shouldRequest(LSA lsa) {
    if (lsa.lsType != LSA.AS_EXTERNAL_LSA)
      return !database.hasRightInstanceOf(lsa);
    else
      return false; //If external LSAs will be supported, write appropriate code here!!!
  }

  // ---------------------------- AreaData.getLSA ----------------------------------- //
  /** Determine whether a particular LSA is stored in the database belonging to this
   *  Area Data Structure. If it does, return a copy of this LSA, otherwise return null.
   *  @param key String representing the unique identifier of an LSA. (see LSA.getKey).
   *  @return LSA containing the desired LSA or null if the LSA could not be found.
   */
  public LSA getLSA(String key) {
    return database.getLSA(key);
  }

  // ------------------------- AreaData.installReceivedLSA ------------------------------ //
  /** Install a new received LSA in the database and return true if installation was 
   *  successful, otherwise false. LSAs installed here are never selforiginated! If an 
   *  LSA is installed successfully, the flooding process is started for it.
   *
   *  @param instLSA an LSA received during database exchange or flooding.
   *  @param neighborID int value, identifying the neighbor who sent the LSA.
   *  @return boolean value indicating the success of the installation.
   */
  public boolean installReceivedLSA(LSA instLSA, int neighborID) {
    boolean isInstalled = database.installRecLSA(instLSA);
    
    if (isInstalled) floodLSA(instLSA, neighborID);
    // flood new installed LSA out some subset of Interfaces.
    return isInstalled;
  }

  // --------------------------- AreaData.hasInstanceOf ------------------------------- //
  /** Check whether an instance of the given LSA exists in the database belonging to this
   *  Area, and if so return true else false. 
   */
  public boolean hasInstanceOf(LSA testLSA) {
    return database.hasInstanceOf(testLSA);
  }

  // -------------------------- AreaData.testInstanceOf ------------------------------- //
  /** Check whether an IDENTICAL/MORE RECENT/LESS RECENT instance of the given LSA exists 
   *  in the database belonging to this Area, and if so return true else false. 
   *  @param testLSA holds the LSA which is compared with the database copy if existing
   *  @param test holds expected result-String of isNewerThan test.
   *  Return true, if the test was positiv false otherwise.
   */
  public boolean testInstanceOf(LSA testLSA, String test) {
      LSA storedLSA = getLSA(testLSA.getKey());
      if (storedLSA == null) return false;
      String compare = database.isNewerThan(testLSA, storedLSA);
      if (compare.equals(test)) return true;
      else return false;
  }

  // -------------------------- AreaData.lsaIsWrapping ----------------------------------- //
  /** Check whether the sequence number of a particular LSA is wrapping and return true if 
   *  so, else false.
   */
  public boolean lsaIsWrapping(LSA testLSA) {
    LSA databaseCopy = getLSA(testLSA.getKey());
    if (databaseCopy == null) return false;
    if ((databaseCopy.lsAge == myOSPF.MAX_AGE) && (databaseCopy.lsSeqnum == myOSPF.MAX_SEQUENCE_NUM))
      return true;
    else return false;
  }

  // ----------------------- AreaData.noNeighborInDataExProcess ------------------------------- //
  /** Check all Neighbors belonging to this Area whether they are in state EXCHANGE or LOADING. 
   *  Return false if there is at least one, else return true. This Method is used by 
   *  "processReceivedLSA()" in the Neighbor class.
   *  
   *  @return boolean indicating that there is no Neighbor in Database Exchange.
   */
  public boolean noNeighborInDataExProcess() {
    Interface nextIntf;
    int nextNeighborState;
    for (Iterator intfs = interfaces.values().iterator(); intfs.hasNext(); ) {
      nextIntf = (Interface)intfs.next();
      for (Iterator nghbrs = nextIntf.neighbors.values().iterator(); nghbrs.hasNext(); ) {
	nextNeighborState = ((Neighbor)nghbrs.next()).state;
	if (nextNeighborState == Neighbor.EXCHANGE || nextNeighborState == Neighbor.LOADING) 
          return false;
      }
    }
    return true;
  }

  // ---------------------- AreaData.canBeRemovedFromDatabase ---------------------------- //
  /** Check whether none of the router's Neighbors are in states Exchange or Loading, and 
   *  maxAgeLSA is no longer contained on any Neighbor's Link State Retransmission List. In
   *  this case maxAgeLSA can be removed from the database, and the method returns true. This
   *  method is used by LSDatabase when aging the LSAs in method "ageAndCheckLSAs".
   */
  public boolean canBeRemovedFromDatabase(LSA maxAgeLSA) {
    Interface nextIntf;
    Neighbor nextNghbr;
    int state;
    for (Iterator intfs = interfaces.values().iterator(); intfs.hasNext(); ) {
      nextIntf = (Interface)intfs.next();
      for (Iterator nghbrs = nextIntf.neighbors.values().iterator(); nghbrs.hasNext(); ) {
	nextNghbr = (Neighbor)nghbrs.next();
        state = nextNghbr.state;
	if ((state == Neighbor.EXCHANGE) || (state == Neighbor.LOADING) ||
            (nextNghbr.lsRetransList.containsKey(maxAgeLSA.getKey()))) return false;
      }
    }
    return true;
  }

  // ----------------------- AreaData.clearLSAFromRetrLists ------------------------------ //
  /** removes a particular LSA from every Neighbor Data Structures link state retransmission 
   *  list whose represented Neighbor is adjacent to this area. */
  public void clearLSAFromRetrLists(String oldLSAKey) {
    for (Iterator intfs = interfaces.values().iterator(); intfs.hasNext(); ) {
      ((Interface)intfs.next()).clearLSAFromRetrLists(oldLSAKey);
    }
  }

  // ------------------------ AreaData.compareIPAddrWith --------------------------------- //
  /** Check whether one of the Ip Interface Addresses of this Area is equal to id and return 
   *  true if so, else false. This method is used by class OSPF in method isSelforiginated.
   */
  public boolean compareIPAddrWith(int id) {
    for (Iterator itfs = interfaces.values().iterator(); itfs.hasNext(); ) {
      if (((Interface)itfs.next()).ipaddr == id) return true;
    }
    return false;
  }

  // -------------------------- AreaData.receivedPacket ---------------------------------- //
  /** After the generic input processing of an OSPF Packet was done by the OSPF
   *  session above, this method will further process the Packet destined for 
   *  this Area. It returns false if further checks fail otherwise true.
   */
  public boolean receivedPacket(Packet pkt, Integer sourceIP, Integer destIP) 
                                                                   throws ProtocolException {
    String message;
    switch (pkt.type) {
      case Packet.HELLO:
      // further processing of a Hello Packet
      if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[2] == true) {
        myOSPF.printMessage(IP_s.IPtoString(destIP.intValue()) + "||Received Hello Packet!||" + 
          "From OSPF Router " + pkt.nh + " (" + IP_s.IPtoString(pkt.router_ID) + ")|" + 
          IP_s.IPtoString(sourceIP.intValue()));
      }
      ((Interface)interfaces.get(destIP)).receivedHello(pkt, sourceIP.intValue());
      break;
      case Packet.DATABASE:
      // further processing of a Database Description Packet
      if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[3] == true) {
	DD_Packet content = (DD_Packet)pkt.payload();
        message = IP_s.IPtoString(destIP.intValue()) + "||Received DD Packet!||" + "From OSPF Router "
                + pkt.nh + " (" + IP_s.IPtoString(pkt.router_ID) + ")|" 
                + IP_s.IPtoString(sourceIP.intValue()) + "\n";
        message += "   Initialize: " + (new Boolean(content.initialize)).toString();
        message += "  More: " + (new Boolean(content.more)).toString();
        message += "  Master/Slave: " + (new Boolean(content.isMaster)).toString();
        message += "  Seqnum: " + Integer.toHexString(content.DD_seq_num);
        myOSPF.printMessage(message);
      }
      ((Interface)interfaces.get(destIP)).receivedDDPacket(pkt, sourceIP);
      break;
      case Packet.LSREQUEST:
      // further processing of a Link State Request Packet
      if (myOSPF.debugOptions[9] == true) {
        message = IP_s.IPtoString(destIP.intValue()) + "||Received LS Request Packet!||" + 
          "From OSPF Router " + pkt.nh + " (" + IP_s.IPtoString(pkt.router_ID) + ")|" + 
          IP_s.IPtoString(sourceIP.intValue());
        LS_RequestPacket requestCont = (LS_RequestPacket)pkt.payload();
        Vector recHead = requestCont.getRequests();
        String nextKey;
        for (Enumeration enumVar = recHead.elements(); enumVar.hasMoreElements(); ) {
	  nextKey = (String)enumVar.nextElement();
          message += "\n   Request for " + nextKey;
        }
        myOSPF.printMessage(message);
      }
      ((Interface)interfaces.get(destIP)).receivedLSRequest(pkt, sourceIP);
      break;
      case Packet.LSUPDATE:
      // further processing of a Link State Update Packet
      if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[4] == true) {
        message = IP_s.IPtoString(destIP.intValue()) + "||Received LS Update Packet!||" + 
                  "From OSPF Router " + pkt.nh + " (" + IP_s.IPtoString(pkt.router_ID) + ")|" + 
	          IP_s.IPtoString(sourceIP.intValue());
        LS_UpdatePacket updateCont = (LS_UpdatePacket)pkt.payload();
        Vector recLSAs = updateCont.getLSAs();
        LSA nextLSA;
        for (Enumeration enumVar = recLSAs.elements(); enumVar.hasMoreElements(); ) {
	  nextLSA = (LSA)enumVar.nextElement();
          message += "\n   Update for " + nextLSA.getKey() + " Seqnum: " +
	             Integer.toHexString(nextLSA.lsSeqnum);
        }
        myOSPF.printMessage(message);
      }
      ((Interface)interfaces.get(destIP)).receivedLSUpdate(pkt, sourceIP);
      break;
      case Packet.LSACK:
      // further processing of a Link State Acknowledgment Packet.
      if (myOSPF.debugOptions[9] == true || myOSPF.debugOptions[4] == true) {
        message = IP_s.IPtoString(destIP.intValue()) + "||Received LS Ack Packet!||" + 
                  "From OSPF Router " + pkt.nh + " (" + IP_s.IPtoString(pkt.router_ID) + ")|" + 
                  IP_s.IPtoString(sourceIP.intValue());
        LS_AckPacket ackCont = (LS_AckPacket)pkt.payload();
        Vector recAcks = ackCont.getHeaders();
        LSA nextAck;
        for (Enumeration enumVar = recAcks.elements(); enumVar.hasMoreElements(); ) {
	  nextAck = (LSA)enumVar.nextElement();
          message += "\n   Ack for " + nextAck.getKey() + " Seqnum: " +
	             Integer.toHexString(nextAck.lsSeqnum);
        }
        myOSPF.printMessage(message);
      }
      ((Interface)interfaces.get(destIP)).receivedLSAck(pkt, sourceIP);
      break;
      default:
      return false;
    }
    return true;
  }

  // ------------------------ AreaData.calculateDijkstraPartI ---------------------------- //
  /** Calculate the first stage of Dijkstra. Return a HashMap holding the RouterEntries for
   *  all area border routers connected to this area.
   *  @return HashMap holding RouterEntries in form of Integer-RouterEntry mappings.
   */
  public HashMap calculateDijkstraPartI() {
    HashMap routerRoutes;
    dijkstra = new Dijkstra(database.getRouterAndNetworkLSAs(), myOSPF, this);
    routerRoutes = dijkstra.firstStage();
    shortestPathTree = dijkstra.getSPTree();
    return routerRoutes;
  }

  // ------------------------ AreaData.calculateDijkstraPartII ---------------------------- //
  /** Calculate the second stage of Dijkstra. Return a HashMap holding the routing table entrys
   *  for all stub networks.
   *  @return HashMap holding all NetworkEntries in form of String-NetworkEntry mappings.
   */
  public HashMap calculateDijkstraPartII() {
    return dijkstra.secondStage();
  }

  // -------------------------- AreaData.printConfiguration ------------------------------- //
  /** print current configuration of the area if necessary. That means if configuration
   *  has changed since last print and one of the following ospf options are set:
   *  "show_area", "show_config", "show_all".
   */
  public void printConfiguration() {
    if ((myOSPF.debugOptions[9] == true || myOSPF.debugOptions[8] == true || 
         myOSPF.debugOptions[5]) && configChanged) {
      String msg = "Configuration of area "+areaID+" changed!\noption field:";
      for (int i=0;i<options.length;i++)
        msg += " " + (new Boolean(options[i])).toString();
      msg += "\nstub default cost: " + stubDefaultCost +
             "\ntransit capability: " + transitCapability +
             "\nattached interfaces:";
      for (Iterator intfs = interfaces.keySet().iterator(); intfs.hasNext(); )
        msg += " " + IP_s.IPtoString(((Integer)intfs.next()).intValue());
      myOSPF.printMessage(msg);
      configChanged = false;
    }
  }

  // -------------------------- AreaData.databaseDebugInfo --------------------------------- //
  /** check whether debug switches for LS database debugging are enabled and the content of the
   *  database has changed. If so, print out an excerpt of the area's LS database.
   */
  public void databaseDebugInfo() {
    if (myOSPF.debugOptions[0] || myOSPF.debugOptions[9]) database.printDatabase();
  }
} // end of class AreaData








